/*!
    \copyright  Copyright (c) 2022 - 2025 Qualcomm Technologies International, Ltd.\n
                All Rights Reserved.\n
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    sink_service
    \brief      Sink service LE state machine implementation
*/

#ifdef ENABLE_LE_SINK_SERVICE

/* local logging */
#include "sink_service_logging.h"

/* local includes */
#include "sink_service_protected.h"
#include "sink_service_le_sm.h"
#include "sink_service_config.h"
#include "sink_service_util.h"
#include "gatt_connect.h"
#include "gatt_service_discovery.h"
#include "gatt.h"
#include "pairing.h"

/* framework includes */

#include <bt_device.h>
#include <rssi_pairing.h>
#include <connection_manager.h>
#include <device_properties.h>
#include <profile_manager.h>
#include <device_list.h>
#include <task_list.h>
#include <device_db_serialiser.h>
#include <unexpected_message.h>
#include <le_audio_client.h>

/* system includes */
#include <bdaddr.h>
#include <stdlib.h>
#include <panic.h>

#define SINK_SERVICE_CLIENT_TASKS_LIST_INIT_CAPACITY 1

/*! \brief Cast a Task to a sink_service_state_machine_t.
    This depends on task_data being the first member of sink_service_state_machine_t. */
#define sinkServiceLeSm_GetTaskForSm(_sm)       sinkService_GetTaskForSm(_sm)
#define sinkServiceLeSm_GetTaskForSm(_sm)       sinkService_GetTaskForSm(_sm)
#define sinkServiceLeSm_GetStateForSm(_sm)      sinkService_GetStateForSm(_sm)

static bool sinkServiceLeSm_IsAllLeAudioClientsConnected(sink_service_state_machine_t *sm)
{
    bool status = TRUE;
    /* Get the leAudioClient connected count. If all the connected set member have moved to
    * proper LEA state - QOS or streaming then we don't need LE Audio initialisation for
    * connected member.
    */
    if (LeAudioClient_GetConnectedClientCount() < sinkServiceUtil_GetConnectedLeDeviceCount(sm))
    {
        status = FALSE;
    }
   return status;
}

static void sinkServiceLeSm_SetWhiteListConnect(sink_service_state_machine_t *sm)
{
     /* At any time either Whitelist connect or Local initiated connect can be active */
     SINK_SERVICE_SET_OPERATION_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT);

     /* Clear the SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST as we have enabled Whitelist Connection */
     SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST);

     DEBUG_LOG_INFO("sinkServiceLeSm_SetWhiteListConnect: sink_service_operations =%x", sm->sink_service_operations);
}

static void sinkServiceLeSm_SetLocalInitiatedConnect(sink_service_state_machine_t *sm)
{
     /* At any time either Whitelist connect or Local initiated connect can be active
      * Clearing Whitelist initatited connect is treated as local initiated connect operation
      */
     SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT);

     DEBUG_LOG_INFO("sinkServiceLeSm_SetLocalInitiatedConnect: sink_service_operations =%x", sm->sink_service_operations);
}

static void sinkServiceLeSm_SetCancelOpenRequestAcceptList(sink_service_state_machine_t *sm)
{
     SINK_SERVICE_SET_OPERATION_REQUEST(sm->sink_service_operations, SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST);

     /* At any time either Whitelist connect or cancel open request accpet list operation can be active */
     SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT);

     DEBUG_LOG_INFO("sinkServiceLeSm_SetCancelOpenRequestAcceptList: sink_service_operations =%x", sm->sink_service_operations);
}

static lea_device_info_t * sinkServiceLeSm_GetLeDeviceInfoByCid(sink_service_state_machine_t *sm, gatt_cid_t cid)
{
    uint8 index;

    for (index = 0; index < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; index++)
    {
        if (sm->lea_device[index].gatt_cid == cid)
        {
            return &sm->lea_device[index];
        }
    }

    return NULL;
}

static bool sinkServiceLeSm_UpdateGattCid(sink_service_state_machine_t *sm,
                                          tp_bdaddr tpaddr,
                                          gatt_cid_t cid)
{
    int i;
    bool cid_added = FALSE;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (BdaddrIsSame(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr, &tpaddr.taddr.addr))
        {
            sm->lea_device[i].gatt_cid = cid;
            cid_added = TRUE;
        }
    }

    return cid_added;
}

static void sinkServiceLeSm_ClearLeDeviceInfo(sink_service_state_machine_t *sm)
{
    int i;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
       memset(&sm->lea_device[i], 0, sizeof(lea_device_info_t));
       sm->lea_device[i].gatt_cid = INVALID_CID;
    }
}

static bool sinkServiceLeSm_IsLeDevicePresent(sink_service_state_machine_t *sm, tp_bdaddr *tpaddr)
{
    int i;
    bool device_present = FALSE;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (BdaddrIsSame(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr, &tpaddr->taddr.addr))
        {
            device_present = TRUE;
        }
    }

    return device_present;
}

static void sinkServiceLeSm_EvaluateAndConnectLeProfiles(sink_service_state_machine_t *sm,
                                                         lea_device_info_t *sink_device_info)
{
    if (sink_device_info->gatt_discovery_completed &&
        sink_device_info->link_encrypted)
    {
#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
        LeAudioClient_ClientRegister(sinkServiceLeSm_GetTaskForSm(sm));
        LeAudioClient_Connect(sink_device_info->gatt_cid);
#else
        UNUSED(sm);
#endif
    }
}

/*! \brief Callback function to handle GATT connection */
static void sinkServiceLeSm_OnGattConnection(gatt_cid_t cid)
{
    tp_bdaddr tpaddr;
    sink_service_state_machine_t *sm = NULL;
    bool gatt_discovery_needed = FALSE;

    DEBUG_LOG("SinkServiceLeSm_HandleGattConnectInd GATT Connected %x", cid);

    if (GattConnect_GetTpaddrFromConnectionId(cid, &tpaddr))
    {
        sm = SinkServiceLeSm_GetSmFromTpaddr(&tpaddr);

        if (sm == NULL)
        {
            /* GATT has connected.SM is still empty.check if CON_MANAGER_TP_CONNECT_IND
             * is still pending to be delivered.
             */
            FOR_EACH_SINK_SM(le_sm)
            {
                if ((le_sm->state == SINK_SERVICE_STATE_PAIRING || le_sm->state == SINK_SERVICE_STATE_CONNECTING_LE_ACL ||
                     le_sm->state == SINK_SERVICE_STATE_CONNECTING_LE_WHITELIST) &&
                    MessagePendingFirst(sinkServiceLeSm_GetTaskForSm(le_sm), CON_MANAGER_TP_CONNECT_IND, NULL))
                {
                    if (BtDevice_GetDeviceForBdAddr(&tpaddr.taddr.addr) == NULL)
                    {
                        /* If device is not yet available, service discovery might have not triggered
                           upon gatt connect indication. So here we create the device and initiate
                           the service discovery manually */
                        gatt_discovery_needed = TRUE;
                    }

                    PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(le_sm, tpaddr));
                    sinkServiceLeSm_UpdateGattCid(le_sm, tpaddr, cid);

                    if (gatt_discovery_needed)
                    {
                        GattServiceDiscovery_StartServiceDiscovery(cid);
                    }
                    break;
                }
            }
        }
        else
        {
            sinkServiceLeSm_UpdateGattCid(sm, tpaddr, cid);
        }
    }
}

/*! \brief Callback function to handle GATT Link encryption notification */
static void sinkServiceLeSm_OnGattLinkEncrypted(gatt_cid_t cid, bool encrypted)
{
    tp_bdaddr tpaddr;
    lea_device_info_t *sink_device_info;
    sink_service_state_machine_t *sm = NULL;

    DEBUG_LOG_INFO("sinkServiceLeSm_OnGattLinkEncrypted LE Link encrypted %d", encrypted);
 
    if (encrypted && GattConnect_GetTpaddrFromConnectionId(cid, &tpaddr))
    {
        sm = SinkServiceLeSm_GetSmFromTpaddr(&tpaddr);
        sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&tpaddr.taddr.addr);
        PanicFalse(sink_device_info != NULL && sm != NULL);

        sink_device_info->link_encrypted = TRUE;
        sinkServiceLeSm_EvaluateAndConnectLeProfiles(sm, sink_device_info);
    }
}

/*! \brief Handle security confirmation */
static void sinkServiceLeSm_HandleSecurityCfm(sink_service_state_machine_t *sm, const PAIRING_SECURITY_CFM_T *cfm)
{
    tp_bdaddr tp_addr;

    DEBUG_LOG_INFO("SinkServiceLeSm_HandleSecurityCfm Error Code %d", cfm->hci_status);

    if (cfm->hci_status == HCI_ERROR_KEY_MISSING)
    {
        tp_addr.transport = TRANSPORT_BLE_ACL;
        memcpy(&tp_addr.taddr, &cfm->typed_addr, sizeof(typed_bdaddr));

        /* The remote has lost the keys. Just disconnect the link */
        sm->hci_reason_code = HCI_ERROR_KEY_MISSING;
        ConManagerReleaseTpAcl(&tp_addr);
    }

    Pairing_UnregisterForSecurityEvents(sinkServiceLeSm_GetTaskForSm(sm));
}

/*! \brief Callback function to handle GATT Disconnect notification */
static void sinkServiceLeSm_OnGattDisconnect(gatt_cid_t gatt_cid)
{
    DEBUG_LOG("sinkServiceLeSm_OnGattDisconnect Cid : %x", gatt_cid);
}

/*! \brief Callback function to handle GATT Disconnect notification */
static const gatt_connect_observer_callback_t sink_service_le_sm_gatt_callback =
{
    .OnEncryptionChanged = sinkServiceLeSm_OnGattLinkEncrypted,
    .OnDisconnection = sinkServiceLeSm_OnGattDisconnect,
    .OnConnection = sinkServiceLeSm_OnGattConnection
};

/* After entering the disconnected state, ensure sink service triggers the next set of actions */
static void sinkServiceLeSm_EvaluateNextActionForDisconnectedState(sink_service_state_machine_t *sm, bool acl_close_initiated_locally)
{
    /* Check if we need to enter the DISABLED state */
    if (!SinkService_IsEnabled())
    {
        SinkServiceLeSm_ClearInstance(sm);
        sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISABLED);
        SinkService_GetTaskData()->pairing_request_pending = FALSE;

        return;
    }
    /* Check if there is a pairing request pending to enter the PAIRING state. */
    else if (SinkService_GetTaskData()->pairing_request_pending)
    {
        SinkServiceLeSm_ClearInstance(sm);
        SinkService_PairRequest();
        SinkService_GetTaskData()->pairing_request_pending = FALSE;

        return;
    }
#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
    /* If there are paired sink devices and connection is lost/closed by remote, add the device to whitelist */
    else if (sinkServiceUtil_AnyPairedSinkLeDevice() && !acl_close_initiated_locally)
    {
        sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_UPDATING_LE_WHITELIST);
        return;
    }
#else
    UNUSED(acl_close_initiated_locally);
#endif

    /* Clear the entire state machine */
    SinkServiceLeSm_ClearInstance(sm);
}

/*! \brief  Gets the identity address type for the bd_addr */
static uint8 sinkServiceLeSm_GetIdentityAddressType(const bdaddr *bd_addr)
{
    uint8 type = TYPED_BDADDR_PUBLIC;

    /* In PDL, static random address gets stored in device_property_bdaddr 
     * Refer ConManagerResolveTpaddr() in btDevice_CreateDeviceWithTpAddr().
     * This means when sink_serviceLe is trying to retrieve bd_addr from device
     * property from sink, it will only get bd_addr but address type information
     * is not available. Addr_type information can be accessed for the said
     * bd_addr using ConnectionGetIdentityAddressType() which can be then used
     * to update addr_type
     */
    if (ConnectionGetIdentityAddressType(bd_addr) == CSR_BT_ADDR_RANDOM)
    {
        type = TYPED_BDADDR_RANDOM;
    }

    return type;
}

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
static bool sinkServiceLeSm_GetTpAddrFromSinkDevice(device_t sink_device, tp_bdaddr *tpaddr)
{
    bool status = FALSE;
    bdaddr addr;

    if (sink_device != NULL && tpaddr != NULL)
    {
        addr = DeviceProperties_GetBdAddr(sink_device);
        tpaddr->transport = TRANSPORT_BLE_ACL;
        tpaddr->taddr.type = sinkServiceLeSm_GetIdentityAddressType(&addr);
        memcpy(&tpaddr->taddr.addr, &addr, sizeof(bdaddr));
        status = TRUE;
    }

    return status;
}

static void sinkServiceLeSm_AddDeviceToWhitelist(sink_service_state_machine_t *sm, uint16 addressCount, tp_bdaddr *addressList)
{
    DEBUG_LOG_INFO("sinkServiceLeSm_AddDeviceToWhitelist: set_whitelist_lock : %d ", SinkService_GetTaskData()->set_whitelist_lock);

    /* Check if set whitelist lock is available */
    if (!SinkService_GetTaskData()->set_whitelist_lock)
    {
        /* Set the add whitelist lock */
        SinkService_GetTaskData()->set_whitelist_lock = TRUE;

        /* Request low latency for this re-connection setup.*/
        ConManagerRequestDefaultQos(cm_transport_ble, cm_qos_low_latency);

        /* Add the list of addresses to acceptor list and issue Open request on CSR_BT_CM_LE_WHITELIST_SET_CFM */
        ConManagerAcceptorListSetRequest(sinkServiceLeSm_GetTaskForSm(sm), addressCount, addressList);

        /* Subscribe to synergy CM events. Cancelling the connect request placed
         * at any point, the sink service will not receive any TP Connect failed event
         * from CM(The CM does not forwards ACL open request failures to higher layers).
         * So the sink service has to directly subscribe for ACL events from synergy.
         * This below code will be removed when proper whitelisting API's are available.
        */
        CmSetEventMaskReqSend(sinkServiceLeSm_GetTaskForSm(sm),
                                (CSR_BT_CM_EVENT_MASK_SUBSCRIBE_ACL_CONNECTION |
                                CSR_BT_CM_EVENT_MASK_SUBSCRIBE_ROLE_CHANGE |
                                CSR_BT_CM_EVENT_MASK_SUBSCRIBE_MODE_CHANGE |
                                CSR_BT_CM_EVENT_MASK_SUBSCRIBE_LOW_ENERGY),
                                CSR_BT_CM_EVENT_MASK_COND_ALL);
    }
}

static void sinkServiceLeSm_ClearLeDeviceInfoByCid(sink_service_state_machine_t *sm, gatt_cid_t cid)
{
    lea_device_info_t *lea_device = sinkServiceLeSm_GetLeDeviceInfoByCid(sm, cid);

    DEBUG_LOG_INFO("sinkServiceLeSm_ClearLeDeviceInfoByCid: cid : 0x%x ", cid);

    PanicNull(lea_device);

    lea_device->gatt_cid = INVALID_CID;
    lea_device->gatt_discovery_completed = FALSE;
}

static void sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sink_service_state_machine_t *sm, gatt_cid_t cid, bool add_cid_in_whitelist)
{
    tp_bdaddr tpaddr;
    unsigned index;
    device_t *devices = NULL;
    deviceType type = DEVICE_TYPE_SINK;
    unsigned num_devices = 0;
    unsigned address_list_count = 0;
    tp_bdaddr *tp_address_list = {0};
    device_t connected_sink_device = sinkServiceUtil_GetConnectedLeDevice(sm);
    uint8 connected_sirk[SIRK_SIZE_CSIP];
    uint8 sirk[SIRK_SIZE_CSIP];
    unsigned count = 0;

    DeviceList_GetAllDevicesWithPropertyValue(device_property_type, &type, sizeof(deviceType), &devices, &num_devices);

    DEBUG_LOG_INFO("sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist: devices : %p, num_devices : %d ", devices, num_devices);
    
    if (devices && num_devices)
    {
        if (connected_sink_device && sinkServiceUtil_GetSirkForDevice(connected_sink_device, &connected_sirk[0]))
        {
             lea_device_info_t *lea_device = NULL;
             DEBUG_LOG_INFO("sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist: CSIP device %p connected", connected_sink_device);

             /* We need to identify and add all non-connected set members in the whitelist
              */
             for (index = 0; index < num_devices; index++)
             {
                 if (sinkServiceUtil_GetSirkForDevice(devices[index], sirk) &&
                     !memcmp(sirk, connected_sirk, SIRK_SIZE_CSIP) &&
                     !sinkServiceUtil_IsDeviceConnected(sm, devices[index]))
                 {
                     DEBUG_LOG_INFO("sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist: Identified non-connected CSIP device index : %x", devices[index]);
                     address_list_count++;
                 }
             }

             /* Do we want to add connected or disconnected device in whitelist */
             if (add_cid_in_whitelist)
             {
                 lea_device = sinkServiceLeSm_GetLeDeviceInfoByCid(sm, cid);
                 PanicNull(lea_device);

                 address_list_count++;
             }

             DEBUG_LOG("sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist: address_list_count : %d", address_list_count);
        
             if (address_list_count)
             {
                 tp_address_list = (tp_bdaddr *)CsrPmemZalloc(address_list_count * sizeof(tp_bdaddr));
             
                 for (index = 0; index < num_devices; index++)
                 {
                     if (sinkServiceUtil_GetSirkForDevice(devices[index], sirk) &&
                         !memcmp(sirk, connected_sirk, SIRK_SIZE_CSIP) &&
                         !sinkServiceUtil_IsDeviceConnected(sm, devices[index]) &&
                         sinkServiceLeSm_GetTpAddrFromSinkDevice(devices[index], &tpaddr))
                     {
                         DEBUG_LOG_INFO("sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist: Device [%d] LAP: %06lx", index, tpaddr.taddr.addr.lap);
                         memcpy(&tp_address_list[count], &tpaddr, sizeof(tp_bdaddr));
                         count++;
                     }
                 }

                 if (add_cid_in_whitelist &&
                     lea_device &&
                     sinkServiceLeSm_GetTpAddrFromSinkDevice(lea_device->sink_device, &tpaddr))
                 {
                     DEBUG_LOG_INFO("LE Audio Device removed: LAP: %06lx", tpaddr.taddr.addr.lap);
                     memcpy(&tp_address_list[count], &tpaddr, sizeof(tp_bdaddr));
                 }
       
                 sinkServiceLeSm_AddDeviceToWhitelist(sm, address_list_count, tp_address_list);
                 CsrPmemFree(tp_address_list);
             }
        }
     }

    free(devices);
}

static void sinkServiceLeSm_AddAllDevicesToWhitelist(sink_service_state_machine_t *sm)
{
    tp_bdaddr tpaddr;
    unsigned index;
    deviceType type = DEVICE_TYPE_SINK;
    bool is_device_lea = FALSE;
    device_t *devices = NULL;
    unsigned num_devices = 0;
    unsigned address_list_count = 0;
    tp_bdaddr *tp_address_list = {0};
    sink_service_device_type_t device_type = SINK_SERVICE_DEVICE_UNKNOWN;

    DeviceList_GetAllDevicesWithPropertyValue(device_property_type, &type, sizeof(deviceType), &devices, &num_devices);

    DEBUG_LOG_INFO("sinkServiceLeSm_AddAllDevicesToWhitelist: devices : %p, num_devices : %d ", devices, num_devices);
    
    if (devices && num_devices)
    {
        for (index = 0; index < num_devices; index++)
        {
            device_type = sinkServiceUtil_GetSinkDeviceType(devices[index]);
            is_device_lea = (device_type == SINK_SERVICE_DEVICE_DUAL || device_type == SINK_SERVICE_DEVICE_LE);

            DEBUG_LOG_INFO("sinkServiceLeSm_AddAllDevicesToWhitelist: is_device_lea : %d", is_device_lea);

            if (is_device_lea)
            {
                address_list_count++;
            }
        }

        DEBUG_LOG("sinkServiceLeSm_AddAllDevicesToWhitelist: address_list_count : %d", address_list_count);

        if (address_list_count)
        {
            tp_address_list = (tp_bdaddr *)CsrPmemZalloc(address_list_count * sizeof(tp_bdaddr));

            for (index = 0; index < num_devices; index++)
            {
                device_type = sinkServiceUtil_GetSinkDeviceType(devices[index]);
                is_device_lea = (device_type == SINK_SERVICE_DEVICE_DUAL || device_type == SINK_SERVICE_DEVICE_LE);

                if (is_device_lea && sinkServiceLeSm_GetTpAddrFromSinkDevice(devices[index], &tpaddr))
                {
                    DEBUG_LOG_INFO("sinkServiceLeSm_AddAllDevicesToWhitelist: Device [%d] LAP: %06lx", index, tpaddr.taddr.addr.lap);
                    memcpy(&tp_address_list[index], &tpaddr, sizeof(tp_bdaddr));
                }
            }

            sinkServiceLeSm_AddDeviceToWhitelist(sm, address_list_count, tp_address_list);
            CsrPmemFree(tp_address_list);
        }
    }

    free(devices);
}

#endif /* ENABLE_SOURCE_ACCEPTOR_LIST */

static void sinkServiceLeSm_ReleaseAllLeAcls(sink_service_state_machine_t *sm)
{
    int i;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        /* Release the LE-ACL if there is an ACL */
        if (!BdaddrIsZero(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr) &&
            ConManagerIsTpConnectedOrConnecting(&sm->lea_device[i].tp_acl_hold_addr))
        {
            /* Set the reason code value as local initiated disconnect, so we don't add the device to 
             * whitelist later 
             */
            sm->hci_reason_code = HCI_ERROR_CONN_TERM_LOCAL_HOST;

            DEBUG_LOG_DEBUG("sinkServiceLeSm_ReleaseAllLeAcls: LAP: %06lx", sm->lea_device[i].tp_acl_hold_addr.taddr.addr.lap);
            ConManagerReleaseTpAcl(&sm->lea_device[i].tp_acl_hold_addr);
        }
    }
}

static device_t sinkServiceLeSm_CreateDeviceIfNew(tp_bdaddr tp_addr)
{
    device_t sink_device;

    sink_device = BtDevice_GetDeviceForBdAddr(&tp_addr.taddr.addr);

    if (sink_device == NULL)
    {
        /* Since device not exists, create a sink device based on the address type. For a random
           address, device type 'DEVICE_TYPE_SINK_LE' will gets created. If it gets paired later,
           then the device will be updated to 'DEVICE_TYPE_SINK' type. 
           For static random address, sink device has to be DEVICE_TYPE_SINK as static random address
           is not resolvable */

        deviceType dev_type = (tp_addr.taddr.type == TYPED_BDADDR_RANDOM && !ConManagerIsTpBdAddrStaticRandomType(&tp_addr)) ? 
                                                     DEVICE_TYPE_SINK_LE : DEVICE_TYPE_SINK;
        sink_device = PanicNull(BtDevice_GetDeviceCreateIfNewWithTpAddr(&tp_addr, dev_type));

        DEBUG_LOG_DEBUG("sinkServiceLeSm_CreateDeviceIfNew Created new sink device %p type %d", sink_device, dev_type);

        PanicFalse(BtDevice_SetFlags(sink_device, DEVICE_FLAGS_NOT_PAIRED, DEVICE_FLAGS_NOT_PAIRED));

        /* Set default properties only for sink device of type 'DEVICE_TYPE_SINK'. For 'DEVICE_TYPE_SINK_LE', this
           will be set after it gets updated to 'DEVICE_TYPE_SINK' type. ie, after pairing. */
        if (dev_type == DEVICE_TYPE_SINK)
        {
            PanicFalse(BtDevice_SetDefaultProperties(sink_device));
        }
    }

    return sink_device;
}

static void sinkServiceLeSm_EnterConnectingLeAcl(sink_service_state_machine_t *sm)
{
    bdaddr addr = DeviceProperties_GetBdAddr(sinkServiceUtil_DetermineSinkDevice(sm));
    MESSAGE_MAKE(cfm, SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T);

    cfm->tpaddr.transport = TRANSPORT_BLE_ACL;
    cfm->tpaddr.taddr.type = sinkServiceLeSm_GetIdentityAddressType(&addr);
    memcpy(&cfm->tpaddr.taddr.addr, &addr, sizeof(bdaddr));
    PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(sm, cfm->tpaddr));

    /* Request low latency during initial connection setup.*/
    ConManagerRequestDefaultQos(cm_transport_ble, cm_qos_low_latency);
    MessageSendConditionally(sinkServiceLeSm_GetTaskForSm(sm), SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE,
                             cfm, ConManagerCreateTpAcl(&cfm->tpaddr));
}

static void sinkServiceLeSm_StateConnectingLeAclHandler(sink_service_state_machine_t * sm, MessageId id, Message message)
{
    switch(id)
    {
        case LE_AUDIO_CLIENT_DISCONNECT_IND:
        {
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
        }
        break;

        case CON_MANAGER_TP_CONNECT_IND:
        {
            const CON_MANAGER_TP_CONNECT_IND_T *msg = (CON_MANAGER_TP_CONNECT_IND_T *) message;
            CsrBtTypedAddr addr;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingLeAclHandler: CON_MANAGER_TP_CONNECT_IND LAP: 0x%06lx",
                             msg->tpaddr.taddr.addr.lap);

            if (!appDeviceTypeIsSink(&msg->tpaddr.taddr.addr) || !ConManagerIsTpAclLocal(&msg->tpaddr))
            {
                DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingLeAclHandler: Ignore connections from non-sink devices");

                /* Ignore connections from non-sink devices (outside of pairing). These
                   are tracked separately by other components (e.g. handset service). */
                Panic();
                return;
            }

            Pairing_RegisterForSecurityEvents(sinkServiceLeSm_GetTaskForSm(sm));
            BdaddrConvertTypedVmToBluestack(&addr, &msg->tpaddr.taddr);

            /* On a reconnection, encrypt the link as a master */
            CsrBtCmSmLeSecurityReqSend(addr,
                                       L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_MASTER_DIRECTED),
                                       0,
                                       DM_SM_SECURITY_ENCRYPTION);

            PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(sm, msg->tpaddr));
        }
        break;

        case SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE:
        {
            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingLeAclHandler SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE");

            const SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T *msg = (SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T *) message;

            if (!ConManagerIsTpConnected(&msg->tpaddr))
            {
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingLeAclHandler: Failed to connect to Lap : 0x%06lx",
                                msg->tpaddr.taddr.addr.lap);
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
        }
        break;

        case GATT_SERVICE_DISCOVERY_COMPLETE:
        {
            const GATT_SERVICE_DISCOVERY_COMPLETE_T *msg = (GATT_SERVICE_DISCOVERY_COMPLETE_T *) message;

            sinkServiceLeSm_HandleServiceDiscoveryComplete(msg->cid, sm);
        }
        break;

        case CON_MANAGER_TP_DISCONNECT_IND:
        {
            const CON_MANAGER_TP_DISCONNECT_IND_T *msg = message;
            uint8 hci_reason_code = sm->hci_reason_code;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingLeAclHandler: CON_MANAGER_TP_DISCONNECT_IND for LAP : 0x%06lx",
                            msg->tpaddr.taddr.addr.lap);
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);

            if (!ConManagerIsTpConnected(&msg->tpaddr) && (hci_reason_code == HCI_ERROR_KEY_MISSING))
            {
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingLeAclHandler Delete Paired device");
                appDeviceDeleteWithTpAddr(&msg->tpaddr);
                ConnectionSmDeleteAuthDeviceReq(msg->tpaddr.taddr.type, &msg->tpaddr.taddr.addr);
            }
        }
        break;

        case LE_AUDIO_CLIENT_CONNECT_IND:
        {
            const LE_AUDIO_CLIENT_CONNECT_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingLeAclHandler: LE_AUDIO_CLIENT_CONNECTED status %d total_devices:%d connected_devices:%d",
                             msg->status, msg->total_devices, msg->connected_devices);

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTED);
            }
            else
            {
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
        }
        break;

        case LE_AUDIO_CLIENT_DEVICE_ADDED_IND:
        {
            const LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingLeAclHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T status %d more_devices_needed %d", msg->status, msg->more_devices_needed);

            if (msg->more_devices_needed)
            {
                DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingLeAclHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T- Try to connect more device(s)");

                /* There are more device to connect to. Try to connect with more devices,
                 * set operation request for local initiated connection
                 */
                sinkServiceLeSm_SetLocalInitiatedConnect(sm);

                /* Move to connected and Scanning state as we are already connected to at least one device */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTED_AND_SCANNING);
            }
        }
        break;

        case PAIRING_SECURITY_CFM:
        {
            sinkServiceLeSm_HandleSecurityCfm(sm, message);
        }
        break;

        default:
        {
            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingLeAclHandler: Unhandled 0x%d", id);
        }
        break;
    }
}

static void sinkServiceLeSm_ExitConnectingLeAcl(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_INFO("sinkServiceLeSm_ExitConnectingLeAcl");
}

static void sinkServiceLeSm_StateConnectedHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    UNUSED(message);

    DEBUG_LOG("sinkServiceLeSm_StateConnectedHandler");

    switch(id)
    {
        case LE_AUDIO_CLIENT_DISCONNECT_IND:
        {
            const LE_AUDIO_CLIENT_DISCONNECT_IND_T *msg = message;

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                /* All device(s) are removed. Move to disconnected state */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                sinkServiceLeSm_ClearLeDeviceInfo(sm);
            }
        }
        break;

        case LE_AUDIO_CLIENT_DEVICE_REMOVED_IND:
        {
            const LE_AUDIO_CLIENT_DEVICE_REMOVED_IND_T *msg = message;

            /* If only one device got disconnected, move to updated LE whitelist sink state.
               Note : If more_devices_present is zero, then we will soon be getting
               message LE_AUDIO_CLIENT_DISCONNECT_IND */
            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS && msg->more_devices_present)
            {
                /* Add this device along with rest of LE CSIP non-connected devices to whitelist */
                sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sm, msg->cid, TRUE);

                /* Remove this device from sink service device list.*/
                sinkServiceLeSm_ClearLeDeviceInfoByCid(sm, msg->cid);

                /* Move the sm state to SINK_SERVICE_STATE_UPDATING_LE_WHITELIST. Calling sinkServiceLeSm_SetState() API
                 * with SINK_SERVICE_STATE_UPDATING_LE_WHITELIST clears the complete instance */
                sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
            }
        }
        break;

        case CON_MANAGER_TP_DISCONNECT_IND:
        {
            lea_device_info_t *sink_device_info;
            const CON_MANAGER_TP_DISCONNECT_IND_T *msg = message;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&msg->tpaddr.taddr.addr);

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedHandler: Disconnected from Lap : 0x%06lx", msg->tpaddr.taddr.addr.lap);

            PanicFalse(sink_device_info != NULL);
            (void) LeAudioClient_Disconnect(sink_device_info->gatt_cid, ConManagerIsTpConnected(&msg->tpaddr));
        }
        break;

        default:
        break;
    }
}

static void sinkServiceLeSm_StateDisabledHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    UNUSED(message);
    UNUSED(id);
    UNUSED(sm);
}

static void sinkServiceLeSm_StateDisconnectedHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    UNUSED(message);
    UNUSED(id);
    UNUSED(sm);
}

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST

static void sinkServiceLeSm_EnterConnectedAndScanningAudioRoute(sink_service_state_machine_t *sm)
{
    tp_bdaddr tpaddr;

    DEBUG_LOG_INFO("sinkServiceLeSm_EnterConnectedAndScanningAudioRoute: Identified index : 0, device:%x", sm->lea_device[0].sink_device);

    /* send the connect confirmaton for the first device as first device is connected only  */
    SinkService_SendConnectedCfm(sm->lea_device[0].sink_device, SINK_SERVICE_TRANSPORT_LE, sink_service_status_success);

    /* Set both voice and audio source as unicast */
    DeviceProperties_SetAudioSource(sm->lea_device[0].sink_device, audio_source_le_audio_unicast_sender);
    DeviceProperties_SetVoiceSource(sm->lea_device[0].sink_device, voice_source_le_audio_unicast_1);

    sinkServiceLeSm_GetTpAddrFromSinkDevice(sm->lea_device[0].sink_device, &tpaddr);

    DEBUG_LOG("sinkServiceLeSm_EnterConnectedAndScanningAudioRoute update MRU to lap : %x", tpaddr.taddr.addr.lap);

    appDeviceUpdateMruDevice(&tpaddr.taddr.addr);
}

static void sinkServiceLeSm_EnterConnectedAndScanning(sink_service_state_machine_t *sm)
{
    /* Here SD first check if the connection is triggered as part of local initiated or whitelist initiated
     * In local initiated SD trigger connection by determining sink from PS store.
     * In whitelist initiated connection, SD initiates connection by enabling WL scan */
    if (!IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
    {
        device_t sink_to_connect;
          
        sink_to_connect = sinkServiceUtil_DetermineSinkDevice(sm);
          
        if (sink_to_connect == NULL)
        {
            DEBUG_LOG_INFO("sinkServiceLeSm_EnterConnectedAndScanning: No paired devices availble");
            if (sinkServiceLeSm_IsAnyLeDeviceConnected(sm))
            {
                /* If there are any LE Devices connected (if we are partially connected to any
                 * coordinated set members) goahead and configure them.
                 */
                (void) LeAudioClient_DeviceDiscoveryFailed();
            }else
            {
                /* There are no coordinated devices found Or the existing members are lost. Move to disconnected state */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
            return;
        }
          
        bdaddr addr = DeviceProperties_GetBdAddr(sink_to_connect);
        MESSAGE_MAKE(cfm, SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T);
          
        cfm->tpaddr.transport = TRANSPORT_BLE_ACL;
        cfm->tpaddr.taddr.type = sinkServiceLeSm_GetIdentityAddressType(&addr);
        memcpy(&cfm->tpaddr.taddr.addr, &addr, sizeof(bdaddr));
        PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(sm, cfm->tpaddr));

        DEBUG_LOG_INFO("sinkServiceLeSm_EnterConnectedAndScanning: Initiating connection");

        /* Request low latency during initial connection setup.*/
        ConManagerRequestDefaultQos(cm_transport_ble, cm_qos_low_latency);
        MessageSendConditionally(sinkServiceLeSm_GetTaskForSm(sm), SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE,
                                 cfm, ConManagerCreateTpAcl(&cfm->tpaddr));
    }
    else if (IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
    {
        sinkServiceLeSm_SetWhiteListConnect(sm);

        /* Initiate a LE-ACL connection to acceptor list.*/
        ConManagerSendAclOpenUseFilterAcceptListRequest();
    }
}

static void sinkServiceLeSm_ExitConnectedAndScanning(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_DEBUG("sinkServiceLeSm_ExitConnectedAndScanning");
}

static void sinkServiceLeSm_StateUpdatingWhitelistHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    DEBUG_LOG("sinkServiceLeSm_StateUpdatingWhitelistHandler id %x", id);

    switch (id)
    {
        case LE_AUDIO_CLIENT_DISCONNECT_IND:
        {
            const LE_AUDIO_CLIENT_DISCONNECT_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateUpdatingWhitelistHandler LE_AUDIO_CLIENT_DISCONNECT_IND received with status =%d",msg->status );

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                unsigned connected_count = sinkServiceUtil_GetConnectedLeDeviceCount(sm);
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler LE_AUDIO_CLIENT_DISCONNECT_IND connected_count =%d",connected_count);

                if (connected_count > 1)
                {
                    /* don't move to disconnected as we are connected with atleast 1 and disconnecting with 1.
                     * This can happen if LEA client is fully disconnected but we got connection indication from
                     * disconnected device again
                     */
                     return;
                }

                /* All device(s) are removed. Move to disconnected state */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                sinkServiceLeSm_ClearLeDeviceInfo(sm);
            }
        }
        break;

        case CON_MANAGER_TP_DISCONNECT_IND:
        {
            lea_device_info_t *sink_device_info;
            const CON_MANAGER_TP_DISCONNECT_IND_T *msg = message;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_INFO("sinkServiceLeSm_StateUpdatingWhitelistHandler: CON_MANAGER_TP_DISCONNECT_IND for LAP : 0x%06lx",
                            msg->tpaddr.taddr.addr.lap);

            sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&msg->tpaddr.taddr.addr);

            if (sink_device_info != NULL)
            {
                /* If WL initiated connection was active and we receive a disconnect request, then
                 * we need to cancel WL connection request. Next action will be evaluated later
                 */
                if (IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
                {
                    sinkServiceLeSm_SetCancelOpenRequestAcceptList(sm);

                    /* Cancel the ACL connect request sent earlier */
                    ConManagerCancelAclOpenRequestForFilterAcceptList();
                }

                /* Disconnect the device */
                (void) LeAudioClient_Disconnect(sink_device_info->gatt_cid, ConManagerIsTpConnected(&msg->tpaddr));
            }

            if (LeAudioClient_IsInInitialisedState())
            {
               /* The LE-ACL link got disconnected in intialised state.
                * Ensure we move to the disconnecting state to re-evaluate what sink service 
                * should do
                */
               sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
        }
        break;

        /* The ACL connect request which sink service placed to connect with the whitelisted remote device got expired. */
        case CM_PRIM:
        {
            CsrBtCmPrim *prim = (CsrBtCmPrim *) message;

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateUpdatingWhitelistHandler CM_PRIM id 0x%x", *prim);

            if (*prim == CSR_BT_CM_LE_WHITELIST_SET_CFM)
            {
                const CsrBtCmLeWhitelistSetCfm *cfm = (CsrBtCmLeWhitelistSetCfm *) message;

                DEBUG_LOG("sinkServiceLeSm_StateUpdatingWhitelistHandler: CSR_BT_CM_LE_WHITELIST_SET_CFM resultCode %d resultSupplier:%d ",
                                cfm->resultCode, cfm->resultSupplier);

                /* Clear the set whitelist lock */
                SinkService_GetTaskData()->set_whitelist_lock = FALSE;

                if (cfm->resultCode == CSR_BT_RESULT_CODE_CM_SUCCESS)
                {
                   if (!SinkService_GetTaskData()->pairing_request_pending)
                    {
                        DEBUG_LOG_WARN("Initiate LE-ACL connection for added whitelist");

                        sinkServiceLeSm_SetWhiteListConnect(sm);

                       /* Initiate a LE-ACL connection.*/
                       if (sinkServiceUtil_IsLeTransportConnected(sm))
                       {
                           sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTED_AND_SCANNING);
                       }
                       else
                       {
                           sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTING_LE_WHITELIST);
                       }
                    }
                }
                else
                {
                    /* In case of failure move to disconnected in order to retrigger update whitelist */
                    sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                }
            }
        }
        break;

        default:
        break;
    }

}

static void sinkServiceLeSm_StateConnectingWhitelistHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    sink_service_state_machine_t *le_sm;
    CsrBtTypedAddr addr;

    DEBUG_LOG_ALWAYS("sinkServiceLeSm_StateConnectingWhitelistHandler id %x", id);

    switch (id)
    {
        case CON_MANAGER_TP_CONNECT_IND:
        {
            /* A device got connected when sink service was in connecting whitelist state */
            const CON_MANAGER_TP_CONNECT_IND_T *ind = (const CON_MANAGER_TP_CONNECT_IND_T *) message;

            if (ind->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_ALWAYS("sinkServiceLeSm_StateConnectingWhitelistHandler enum:TRANSPORT_T:%d type %d [%04x,%02x,%06lx] ",
                   ind->tpaddr.transport,
                   ind->tpaddr.taddr.type,
                   ind->tpaddr.taddr.addr.nap,
                   ind->tpaddr.taddr.addr.uap,
                   ind->tpaddr.taddr.addr.lap);

            if (!appDeviceTypeIsSink(&ind->tpaddr.taddr.addr))
            {
                return;
            }

            /* Use the first instance in the list for this request */
            le_sm = SinkService_GetTaskData()->state_machines;

            PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(le_sm, ind->tpaddr));

            /* On a reconnection, encrypt the link as a master */
            BdaddrConvertTypedVmToBluestack(&addr, &ind->tpaddr.taddr);

            Pairing_RegisterForSecurityEvents(sinkServiceLeSm_GetTaskForSm(sm));

            /* If all good, trigger a request to encrypt the link */
            CsrBtCmSmLeSecurityReqSend(addr,
                                       L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_MASTER_DIRECTED),
                                       0,
                                       DM_SM_SECURITY_ENCRYPTION);

            /* Note:
             * Sink service should stay in the current state itself.When GATT service discovery completes
             * it will get a GATT_SERVICE_DISCOVERY_COMPLETE notification and moves to connecting LE Profiles.
             */
        }
        break;

        case GATT_SERVICE_DISCOVERY_COMPLETE:
        {
            const GATT_SERVICE_DISCOVERY_COMPLETE_T *msg = (GATT_SERVICE_DISCOVERY_COMPLETE_T *) message;

            /* GATT Service discovery completed. Try to connect LE Profiles now */
            sinkServiceLeSm_HandleServiceDiscoveryComplete(msg->cid, sm);
        }
        break;

        case CON_MANAGER_TP_DISCONNECT_IND:
        {
            lea_device_info_t *sink_device_info;
            const CON_MANAGER_TP_DISCONNECT_IND_T *msg = message;
            uint8 hci_reason_code = sm->hci_reason_code;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingWhitelistHandler: CON_MANAGER_TP_DISCONNECT_IND for LAP : 0x%06lx",
                            msg->tpaddr.taddr.addr.lap);

            sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&msg->tpaddr.taddr.addr);

            if (sink_device_info != NULL)
            {
                (void) LeAudioClient_Disconnect(sink_device_info->gatt_cid, ConManagerIsTpConnected(&msg->tpaddr));
            }

            /* The LE-ACL link got disconnected. Ensure we move to the disconnecting state to re-evaluate what sink service 
             * should do
             */
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);

            if (!ConManagerIsTpConnected(&msg->tpaddr) && (hci_reason_code == HCI_ERROR_KEY_MISSING))
            {
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingWhitelistHandler Delete Paired device");
                appDeviceDeleteWithTpAddr(&msg->tpaddr);
                ConnectionSmDeleteAuthDeviceReq(msg->tpaddr.taddr.type, &msg->tpaddr.taddr.addr);
            }
        }
        break;

        case LE_AUDIO_CLIENT_DEVICE_ADDED_IND:
        {
            const LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingWhitelistHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T status %d more_devices_needed %d", msg->status, msg->more_devices_needed);

            if (msg->more_devices_needed)
            {
                 DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingWhitelistHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T- Try to connect another device by adding in WL");
                /* There are more device for WL connect to */
                sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sm, msg->cid, FALSE);

                /* Move the sm state to SINK_SERVICE_STATE_UPDATING_LE_WHITELIST. 
                 * Note :Don't call sinkServiceLeSm_SetState() API with SINK_SERVICE_STATE_UPDATING_LE_WHITELIST
                 * as it add all devices in WL and clears the complete sm instance */
                sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
            }
        }
        break;

        case LE_AUDIO_CLIENT_CONNECT_IND:
        {
            const LE_AUDIO_CLIENT_CONNECT_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectingWhitelistHandler: LE_AUDIO_CLIENT_CONNECTED status %d total_devices:%d connected_devices:%d",
                            msg->status, msg->total_devices, msg->connected_devices);

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTED);
            }
            else
            {
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
        }
        break;

        /* The ACL connect request which sink service placed to connect with the whitelisted remote device got expired. */
        case CM_PRIM:
        {
            CsrBtCmPrim *prim = (CsrBtCmPrim *) message;

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectingWhitelistHandler CM_PRIM id 0x%x", *prim);

            if (*prim == CSR_BT_CM_LE_EVENT_CONNECTION_IND)
            {
                /* Only handle Connection failures here. */
                const CsrBtCmLeEventConnectionInd *ind = (CsrBtCmLeEventConnectionInd *) message;

                if (ind->connectStatus == CSR_BT_LE_EVENT_CONNECT_FAIL)
                {
                    /* If user initiated fresh pairing, then move to disconnected state and starts the pairing from there */
                    if (SinkService_GetTaskData()->pairing_request_pending)
                    {
                        /* Unsubcribe to synergy CM events and move to disconnected state */
                        CmSetEventMaskReqSend(sinkServiceLeSm_GetTaskForSm(sm),
                                              CSR_BT_CM_EVENT_MASK_SUBSCRIBE_NONE,
                                              CSR_BT_CM_EVENT_MASK_COND_NA);

                        /* Set the value of reason code as locally initiated disconnect, so we don't re-enter
                         * into whitelisting again.
                         */
                        sm->hci_reason_code = HCI_ERROR_CONN_TERM_LOCAL_HOST;
                        sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                    }
                    else
                    {
                        /* Keep on trying to connect with whitelisted devices */
                        ConManagerSendAclOpenUseFilterAcceptListRequest();
                    }
                }
            }
        }
        break;

        case PAIRING_SECURITY_CFM:
        {
            sinkServiceLeSm_HandleSecurityCfm(sm, message);
        }
        break;

        default:
        break;
    }
}

static void sinkServiceLeSm_StateConnectedAndScanningHandler(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    sink_service_state_machine_t *le_sm;
    CsrBtTypedAddr addr;

    DEBUG_LOG("sinkServiceLeSm_StateConnectedAndScanningHandler");

    switch(id)
    {
        case LE_AUDIO_CLIENT_DISCONNECT_IND:
        {
            const LE_AUDIO_CLIENT_DISCONNECT_IND_T *msg = message;

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                unsigned connected_count = sinkServiceUtil_GetConnectedLeDeviceCount(sm);
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler LE_AUDIO_CLIENT_DISCONNECT_IND connected_count =%d",connected_count);

                if (connected_count > 1)
                {
                    /* don't move to disconnected as we are connected with atleast 1 and disconnecting with 1.
                     * This can happen if LEA client is fully disconnected but we got connection indication from
                     * disconnected device again
                     */
                     return;
                }

                /* All device(s) are removed. Move to disconnected state */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                sinkServiceLeSm_ClearLeDeviceInfo(sm);
            }
        }
        break;

        case LE_AUDIO_CLIENT_DEVICE_REMOVED_IND:
        {
            const LE_AUDIO_CLIENT_DEVICE_REMOVED_IND_T *msg = message;
            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: LE_AUDIO_CLIENT_DEVICE_REMOVED_IND status =%d, more_devices_present=%d", msg->status, msg->more_devices_present);

            /* If only one device got disconnected, move to updated LE whitelist sink state.
               Note : If more_devices_present is zero, then we will soon be getting
               message LE_AUDIO_CLIENT_DISCONNECT_IND */
            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS && msg->more_devices_present)
            {
                /* Add this device along with rest of LE CSIP non-connected devices to whitelist */
                sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sm, msg->cid, TRUE);

                /* Remove this device from sink service device list.*/
                sinkServiceLeSm_ClearLeDeviceInfoByCid(sm, msg->cid);

                /* Move the sm state to SINK_SERVICE_STATE_UPDATING_LE_WHITELIST. Calling sinkServiceLeSm_SetState() API
                 * with SINK_SERVICE_STATE_UPDATING_LE_WHITELIST clears the complete instance */
                sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
            }
        }
        break;

        case SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE:
        {
            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE");

            const SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T *msg = (SINK_SERVICE_INTERNAL_CONNECT_ACL_COMPLETE_T *) message;

            if (!ConManagerIsTpConnected(&msg->tpaddr))
            {
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: Failed to connect to Lap : 0x%06lx",
                                msg->tpaddr.taddr.addr.lap);
                /* We are connected with at least one device, move to connected and scanning state 
                 * and then re-enable WL scan for failed device(s) 
                 */

                /* Clear the sink device info */
                sinkServiceLeSm_ClearLeDeviceInfoByAddr(sm, &msg->tpaddr.taddr.addr);

                /* If there are any LE Devices connected (if we are partially connected to any
                 * coordinated set members) goahead and configure them.
                 */
                (void) LeAudioClient_DeviceDiscoveryFailed();
            }
            else
            {
                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: Successfully connected to Lap : 0x%06lx",
                                msg->tpaddr.taddr.addr.lap);
            }
        }
        break;

        case CON_MANAGER_TP_CONNECT_IND:
        {
            /* A device got connected when sink service was in connecting whitelist state */
            const CON_MANAGER_TP_CONNECT_IND_T *ind = (const CON_MANAGER_TP_CONNECT_IND_T *) message;

            if (ind->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }

            DEBUG_LOG_ALWAYS("sinkServiceLeSm_StateConnectedAndScanningHandler enum:TRANSPORT_T:%d type %d [%04x,%02x,%06lx] ",
                   ind->tpaddr.transport,
                   ind->tpaddr.taddr.type,
                   ind->tpaddr.taddr.addr.nap,
                   ind->tpaddr.taddr.addr.uap,
                   ind->tpaddr.taddr.addr.lap);

            if (!appDeviceTypeIsSink(&ind->tpaddr.taddr.addr))
            {
                return;
            }

            /* Use the first instance in the list for this request */
            le_sm = SinkService_GetTaskData()->state_machines;

            PanicFalse(sinkServiceLeSm_AddLeDeviceInfo(le_sm, ind->tpaddr));

            /* On a reconnection, encrypt the link as a master */
            BdaddrConvertTypedVmToBluestack(&addr, &ind->tpaddr.taddr);

            Pairing_RegisterForSecurityEvents(sinkServiceLeSm_GetTaskForSm(sm));

            /* If all good, trigger a request to encrypt the link */
            CsrBtCmSmLeSecurityReqSend(addr,
                                       L2CA_CONFLAG_ENUM(L2CA_CONNECTION_LE_MASTER_DIRECTED),
                                       0,
                                       DM_SM_SECURITY_ENCRYPTION);

            /* Note:
             * Sink service should stay in the current state itself.When GATT service discovery completes
             * it will get a GATT_SERVICE_DISCOVERY_COMPLETE notification and moves to connecting LE Profiles.
             */
        }
        break;

        case GATT_SERVICE_DISCOVERY_COMPLETE:
        {
            const GATT_SERVICE_DISCOVERY_COMPLETE_T *msg = (GATT_SERVICE_DISCOVERY_COMPLETE_T *) message;

            /* GATT Service discovery completed. Try to connect LE Profiles now */
            sinkServiceLeSm_HandleServiceDiscoveryComplete(msg->cid, sm);
        }
        break;

        case LE_AUDIO_CLIENT_DEVICE_ADDED_IND:
        {
            const LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T status %d more_devices_needed %d", msg->status, msg->more_devices_needed);

            if (msg->more_devices_needed)
            {
                if (!IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
                {               
                   sinkServiceLeSm_EnterConnectedAndScanning(sm);
                }
                else if (IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
                {                   
                    DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: LE_AUDIO_CLIENT_DEVICE_ADDED_IND_T- Try to connect another device by adding in WL");

                    /* There are more device for WL connect to */
                    sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sm, msg->cid, FALSE);

                    /* Move the sm state to SINK_SERVICE_STATE_UPDATING_LE_WHITELIST. 
                     * Note :Don't call sinkServiceLeSm_SetState() API with SINK_SERVICE_STATE_UPDATING_LE_WHITELIST
                     * as it add all devices in WL and clears the complete sm instance */
                    sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
                }
            }
        }
        break;

        case LE_AUDIO_CLIENT_CONNECT_IND:
        {
            const LE_AUDIO_CLIENT_CONNECT_IND_T *msg = message;

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: LE_AUDIO_CLIENT_CONNECTED status %d total_devices:%d connected_devices:%d",
                            msg->status, msg->total_devices, msg->connected_devices);

            if (msg->status == LE_AUDIO_CLIENT_STATUS_SUCCESS)
            {
                if (msg->connected_devices < msg->total_devices)
                {            
                    /* There are 2 possible cases are 
                    * a) SD is not connected to all the devices but paired to all.
                    * b) SD is not connected to all the devices and paired to not all.
                    */
                    if (sinkServiceUtil_GetSetMembersCount(sm) == msg->connected_devices)
                    {
                         /* Inform USB module about sink device for the connected device */
                         sinkServiceLeSm_EnterConnectedAndScanningAudioRoute(sm);

                         /* SD is not paired with all set member(s), enabling pairing */
                         /* To do */
                    }
                    else if (sinkServiceUtil_GetSetMembersCount(sm) == msg->total_devices)
                    {
                        /* Inform USB module about sink device for the connected device */
                        sinkServiceLeSm_EnterConnectedAndScanningAudioRoute(sm);

                        /* SD is paired with all set members but not connected to all.
                        * Identify non-connected CSIP devices and add to WL
                        */
                        sinkServiceLeSm_EvaluateAddCsipDevicesToWhitelist(sm, 0, FALSE);

                        /* Move the sm state to SINK_SERVICE_STATE_UPDATING_LE_WHITELIST. 
                         * Calling sinkServiceLeSm_SetState() API with
                         * SINK_SERVICE_STATE_UPDATING_LE_WHITELIST clears the complete instance
                         */
                        sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
                    }
                 }
                 else
                 {
                     sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTED);
                 }
            }
            else
            {
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
            }
        }
        break;

        case CON_MANAGER_TP_DISCONNECT_IND:
        {
            lea_device_info_t *sink_device_info;
            const CON_MANAGER_TP_DISCONNECT_IND_T *msg = message;

            if (msg->tpaddr.transport != TRANSPORT_BLE_ACL)
            {
                /* Ignore if not LE ACL */
                return;
            }
          
            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: CON_MANAGER_TP_DISCONNECT_IND for LAP : 0x%06lx",
                            msg->tpaddr.taddr.addr.lap);

            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler: Disconnected from Lap : 0x%06lx", msg->tpaddr.taddr.addr.lap);

            sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&msg->tpaddr.taddr.addr);

            if (sink_device_info != NULL)
            {
                /* If WL initiated connection was active and we receive a disconnect request, then
                 * we need to cancel WL connection request. Next action will be evaluated later
                 */
                if (IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_WHITELIST_INITIATED_CONNECT))
                {
                    sinkServiceLeSm_SetCancelOpenRequestAcceptList(sm);

                    /* Cancel the ACL connect request sent earlier */
                    ConManagerCancelAclOpenRequestForFilterAcceptList();
                }            

                /* Disconnect the device */
                (void) LeAudioClient_Disconnect(sink_device_info->gatt_cid, ConManagerIsTpConnected(&msg->tpaddr));
            }
            
             if (LeAudioClient_IsInInitialisedState())
             {
                /* The LE-ACL link got disconnected in intialised state.
                 * Ensure we move to the disconnecting state to re-evaluate what sink service 
                 * should do
                 */
                sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
             }
        }
        break;

        /* The ACL connect request which sink service placed to connect with the whitelisted remote device got expired. */
        case CM_PRIM:
        {
            CsrBtCmPrim *prim = (CsrBtCmPrim *) message;

            DEBUG_LOG_DEBUG("sinkServiceLeSm_StateConnectedAndScanningHandler CM_PRIM id 0x%x", *prim);

            if (*prim == CSR_BT_CM_LE_EVENT_CONNECTION_IND)
            {
                /* Only handle Connection failures here. */
                const CsrBtCmLeEventConnectionInd *ind = (CsrBtCmLeEventConnectionInd *) message;
                tp_bdaddr tpaddr;
                
                tpaddr.transport = TRANSPORT_BLE_ACL;
                BdaddrConvertTypedBluestackToVm(&tpaddr.taddr, &ind->deviceAddr);

                DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler status: %06lx", ind->connectStatus);

                if (ind->connectStatus == CSR_BT_LE_EVENT_CONNECT_FAIL)
                {
                    /* If user initiated fresh pairing, then move to disconnected state and starts the pairing from there */
                    if (SinkService_GetTaskData()->pairing_request_pending)
                    {
                        lea_device_info_t *sink_device_info;

                        /* Unsubcribe to synergy CM events and move to disconnected state */
                        CmSetEventMaskReqSend(sinkServiceLeSm_GetTaskForSm(sm),
                                              CSR_BT_CM_EVENT_MASK_SUBSCRIBE_NONE,
                                              CSR_BT_CM_EVENT_MASK_COND_NA);

                        /* Set the value of reason code as locally initiated disconnect, so we don't re-enter
                         * into whitelisting again.
                         */
                        sm->hci_reason_code = HCI_ERROR_CONN_TERM_LOCAL_HOST;
                        sink_device_info = sinkServiceLeSm_GetLeDeviceInfoByAddr(sm, (bdaddr*)&tpaddr.taddr.addr);

                        if (sink_device_info != NULL)
                        {
                            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler CSR_BT_LE_EVENT_CONNECT_FAIL, now clean up for LAP: %06lx", tpaddr.taddr.addr.lap);
                            (void) LeAudioClient_Disconnect(sink_device_info->gatt_cid, ConManagerIsTpConnected(&tpaddr));
                        }
                        else
                        {
                            DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler CSR_BT_LE_EVENT_CONNECT_FAIL, sink_device_info == NULL for LAP: %06lx", tpaddr.taddr.addr.lap);
                        }

                        sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
                    }
                    else
                    {
                         /* if cancel_open_request_accept_list is set means there has been a disconnection with another device when WL scan was also active,
                          * so acceptor list will change and re-trigger again. So, don't trigger old acceptor list again.
                          */
                         if(!IS_SINK_SERVICE_OPERATIONS_SET_BY_REQUEST(sm->sink_service_operations, SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST))
                         {
                             if (!sinkServiceLeSm_IsAllLeAudioClientsConnected(sm))
                             {
 
                                 /* Cancel whitelist scan flag and open request flag */
                                 SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, (SINK_SERVICE_WHITELIST_INITIATED_CONNECT | SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST));

                                 /* If there are any LE Devices connected (if we are partially connected to any
                                  * coordinated set members) goahead and configure them.
                                  */
                                 (void) LeAudioClient_DeviceDiscoveryFailed();
                             }
                             else
                             {
                                 DEBUG_LOG_INFO("sinkServiceLeSm_StateConnectedAndScanningHandler Trying whitelist connect again..");

                                 /* Keep on trying to connect with whitelisted devices */
                                 ConManagerSendAclOpenUseFilterAcceptListRequest();
                             }
                         }
                    }
                }
            }
        }
        break;

        default:
        break;
    }
}

#endif

/* State exit functions */
static void sinkServiceLeSm_ExitDisconnected(sink_service_state_machine_t *sm)
{
    DEBUG_LOG_DEBUG("sinkServiceLeSm_ExitDisconnected");

    ConManagerRegisterTpConnectionsObserver(cm_transport_ble, sinkServiceLeSm_GetTaskForSm(sm));
    GattServiceDiscovery_ClientRegister(sinkServiceLeSm_GetTaskForSm(sm));
}

static void sinkServiceLeSm_ExitConnected(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_DEBUG("sinkServiceLeSm_ExitConnected");
}

static void sinkServiceLeSm_ExitDisabled(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_DEBUG("sinkServiceLeSm_ExitDisableds");
}

/* State enter functions */
static void sinkServiceLeSm_EnterDisconnected(sink_service_state_machine_t *sm)
{
    DEBUG_LOG_DEBUG("sinkServiceLeSm_EnterDisconnected");

    /* sm is disconnected, clear the flags */
    SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, (SINK_SERVICE_WHITELIST_INITIATED_CONNECT | SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST));

    /* Release all the LE-ACL's */
    sinkServiceLeSm_ReleaseAllLeAcls(sm);

    if (sm->lea_device[0].sink_device != NULL)
    {
        SinkService_SendDisconnectedCfm(sm->lea_device[0].sink_device);

        if (Device_IsPropertySet(sm->lea_device[0].sink_device, device_property_audio_source))
        {
            /* Remove the audio and voice sources */
            DeviceProperties_RemoveAudioSource(sm->lea_device[0].sink_device);
            DeviceProperties_RemoveVoiceSource(sm->lea_device[0].sink_device);
        }
        sinkServiceLeSm_DeleteDeviceIfNotPaired(sm);
    }

    /* Sink service is in disconnected state. Evaluate the following conditions:
     * 1. If user has initiated fresh pairing, ensure sink service kicks in RSSI pairing. (OR)
     * 2. If Link is lost because of OETC, ensure sink service adds the MRU device to whitelist. (OR)
     * 3. If Sink service is disabled, enter disabled state.
     * 4. If none of the above are true, stay in disconnected state.
     */
     bool hci_reason_code = ((sm->hci_reason_code == HCI_ERROR_CONN_TERM_LOCAL_HOST) || 
                                (sm->hci_reason_code == HCI_ERROR_KEY_MISSING) ) ? TRUE : FALSE;
     sinkServiceLeSm_EvaluateNextActionForDisconnectedState(sm, hci_reason_code);
}

static void sinkServiceLeSm_EnterConnected(sink_service_state_machine_t *sm)
{
    tp_bdaddr tpaddr;
    int i;

    DEBUG_LOG_INFO("sinkServiceLeSm_EnterConnected");

    /* sm is connected, clear the flags */
    SINK_SERVICE_CLEAR_OPERATION_REQUEST(sm->sink_service_operations, (SINK_SERVICE_WHITELIST_INITIATED_CONNECT | SINK_SERVICE_CANCEL_OPEN_REQUEST_ACCEPT_LIST));

    /* ToDo: sm is connected, release the ACL(why we need to release the LE-ACL?) */
    /* sinkServiceLeSm_ReleaseAcl(sm); */
    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        /* For now send the connect confirmaton for the first device(for Split-Cig). For CSIP
         * we need to determine which device needs to be propagated to USB or a change in logic
         * is needed
         */
        SinkService_SendConnectedCfm(sm->lea_device[i].sink_device, SINK_SERVICE_TRANSPORT_LE, sink_service_status_success);

        /* Set both voice and audio source as unicast */
        DeviceProperties_SetAudioSource(sm->lea_device[i].sink_device, audio_source_le_audio_unicast_sender);
        DeviceProperties_SetVoiceSource(sm->lea_device[i].sink_device, voice_source_le_audio_unicast_1);

        sinkServiceLeSm_GetTpAddrFromSinkDevice(sm->lea_device[i].sink_device, &tpaddr);

        DEBUG_LOG("sinkServiceLeSm_EnterConnected update MRU to lap : %x", tpaddr.taddr.addr.lap);

        appDeviceUpdateMruDevice(&tpaddr.taddr.addr);

        break;
    }
}

static void sinkServiceLeSm_EnterDisabled(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_INFO("sinkServiceLeSm_EnterDisabled");
}

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
static void sinkServiceLeSm_ExitUpdatingWhitelist(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_INFO("sinkServiceLeSm_ExitUpdatingWhitelist");
}

static void sinkServiceLeSm_ExitConnectingWhitelist(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    DEBUG_LOG_INFO("sinkServiceLeSm_ExitConnectingWhitelist");
}

static void sinkServiceLeSm_EnterUpdatingWhitelist(sink_service_state_machine_t *sm)
{
    /* Add paired devices to LE Acceptor list */
    sinkServiceLeSm_AddAllDevicesToWhitelist(sm);
    SinkServiceLeSm_ClearInstance(sm);
    sm->state = SINK_SERVICE_STATE_UPDATING_LE_WHITELIST;
}

static void sinkServiceLeSm_EnterConnectingWhitelist(sink_service_state_machine_t *sm)
{
    UNUSED(sm);

    /* Initiate a LE-ACL connection to acceptor list.*/
    ConManagerSendAclOpenUseFilterAcceptListRequest();
}

void SinkServiceLeSm_RemoveAllDevicesFromWhitelist(void)
{
    /* Clear the entire accept list devices */
    ConManagerSendClearAllDevicesFromLeAcceptListRequest();
}

#endif /* ENABLE_SOURCE_ACCEPTOR_LIST */

void sinkServiceLeSm_SetState(sink_service_state_machine_t *sm, sink_service_state_t state)
{
    sink_service_state_t old_state = sm->state;

    /* It is not valid to re-enter the same state */
    if (old_state == state)
    {
        return;
    }

    DEBUG_LOG_INFO("SinkService: enum:sink_service_state_t:%d -> enum:sink_service_state_t:%d", old_state, state);

    /* Handle state exit functions */
    switch (old_state)
    {
        case SINK_SERVICE_STATE_DISCONNECTED:
            sinkServiceLeSm_ExitDisconnected(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTING_LE_ACL:
            sinkServiceLeSm_ExitConnectingLeAcl(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTED:
            sinkServiceLeSm_ExitConnected(sm);
        break;

        case SINK_SERVICE_STATE_DISABLED:
            sinkServiceLeSm_ExitDisabled(sm);
        break;

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
        case SINK_SERVICE_STATE_UPDATING_LE_WHITELIST:
            sinkServiceLeSm_ExitUpdatingWhitelist(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTING_LE_WHITELIST:
            sinkServiceLeSm_ExitConnectingWhitelist(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTED_AND_SCANNING:
            sinkServiceLeSm_ExitConnectedAndScanning(sm);
        break;
#endif

        default:
        break;
    }

    /* Set new state */
    sm->state = state;

    /* Handle state entry functions */
    switch (sm->state)
    {
        case SINK_SERVICE_STATE_DISCONNECTED:
            sinkServiceLeSm_EnterDisconnected(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTING_LE_ACL:
            sinkServiceLeSm_EnterConnectingLeAcl(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTED:
            sinkServiceLeSm_EnterConnected(sm);
        break;

        case SINK_SERVICE_STATE_DISABLED:
            sinkServiceLeSm_EnterDisabled(sm);
        break;

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
        case SINK_SERVICE_STATE_UPDATING_LE_WHITELIST:
            sinkServiceLeSm_EnterUpdatingWhitelist(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTING_LE_WHITELIST:
            sinkServiceLeSm_EnterConnectingWhitelist(sm);
        break;

        case SINK_SERVICE_STATE_CONNECTED_AND_SCANNING:
            sinkServiceLeSm_EnterConnectedAndScanning(sm);
        break;
#endif

        default:
        break;
    }

    SinkService_UpdateUi();
}

void SinkServiceLeSm_HandleMessage(sink_service_state_machine_t *sm, MessageId id, Message message)
{
    switch (sm->state)
    {
        case SINK_SERVICE_STATE_CONNECTING_LE_ACL:
            sinkServiceLeSm_StateConnectingLeAclHandler(sm, id, message);
        break;

        case SINK_SERVICE_STATE_CONNECTED:
             sinkServiceLeSm_StateConnectedHandler(sm, id, message);
        break;

        case SINK_SERVICE_STATE_CONNECTED_AND_SCANNING:
             sinkServiceLeSm_StateConnectedAndScanningHandler(sm, id, message);
        break;

        case SINK_SERVICE_STATE_DISABLED:
            sinkServiceLeSm_StateDisabledHandler(sm, id, message);
        break;

        case SINK_SERVICE_STATE_DISCONNECTED:
            sinkServiceLeSm_StateDisconnectedHandler(sm, id, message);
        break;

#ifdef ENABLE_SOURCE_ACCEPTOR_LIST
        case SINK_SERVICE_STATE_UPDATING_LE_WHITELIST:
            sinkServiceLeSm_StateUpdatingWhitelistHandler(sm, id, message);
        break;

        case SINK_SERVICE_STATE_CONNECTING_LE_WHITELIST:
            sinkServiceLeSm_StateConnectingWhitelistHandler(sm, id, message);
        break;
#endif
        default:
            UnexpectedMessage_HandleMessage(id);
        break;
    }
}

/* Public functions */
/* Handle GATT Service discovery complete message */
void sinkServiceLeSm_HandleServiceDiscoveryComplete(gatt_cid_t cid, sink_service_state_machine_t *sm)
{
    int i;

    DEBUG_LOG_DEBUG("sinkServiceLeSm_HandleServiceDiscoveryComplete %d", cid);

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (sm->lea_device[i].gatt_cid == cid)
        {
            sm->lea_device[i].gatt_discovery_completed = TRUE;
            sinkServiceLeSm_EvaluateAndConnectLeProfiles(sm, &sm->lea_device[i]);
            return;
        }
    }
}

lea_device_info_t * sinkServiceLeSm_GetLeDeviceInfoByAddr(sink_service_state_machine_t *sm, bdaddr *addr)
{
    int i;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (BdaddrIsSame(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr, addr))
        {
            return &sm->lea_device[i];
        }
    }

    return NULL;
}

bool sinkServiceLeSm_IsAnyLeDeviceConnected(sink_service_state_machine_t *sm)
{
    int i;
    bool device_connected = FALSE;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (sm->lea_device[i].gatt_cid != INVALID_CID &&
            sm->lea_device[i].link_encrypted &&
            sm->lea_device[i].gatt_discovery_completed)
        {
            device_connected = TRUE;
            break;
        }
    }

    return device_connected;
}

bool sinkServiceLeSm_AddLeDeviceInfo(sink_service_state_machine_t *sm, tp_bdaddr tp_addr)
{
    int i;
    bool device_added = FALSE;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (BdaddrIsSame(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr, &tp_addr.taddr.addr))
        {
            device_added = TRUE;
            break;
        }

        if (BdaddrIsZero(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr))
        {
            sm->lea_device[i].tp_acl_hold_addr = tp_addr;
            sm->lea_device[i].sink_device = sinkServiceLeSm_CreateDeviceIfNew(tp_addr);

            DEBUG_LOG_INFO("sinkServiceLeSm_AddLeDeviceInfo sink_device 0x%p", sm->lea_device[i].sink_device);

            device_added = TRUE;
            break;
        }
    }

    return device_added;
}

void sinkServiceLeSm_ClearLeDeviceInfoByAddr(sink_service_state_machine_t *sm, bdaddr *addr)
{
    int i;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (BdaddrIsSame(&sm->lea_device[i].tp_acl_hold_addr.taddr.addr, addr))
        {
            memset(&sm->lea_device[i], 0, sizeof(lea_device_info_t));
            sm->lea_device[i].gatt_cid = INVALID_CID;
            return;
        }
    }
}

bool sinkServiceLeSm_DeviceIsPaired(device_t device)
{
    uint16 flags = DEVICE_FLAGS_NO_FLAGS;

    PanicNull(device);
    appDeviceGetFlagsForDevice(device, &flags);

    return (flags & DEVICE_FLAGS_NOT_PAIRED) ? FALSE : TRUE;
}

/* Iterate through all the LE devices and delete the device if not paired */
void sinkServiceLeSm_DeleteDeviceIfNotPaired(sink_service_state_machine_t *sm)
{
    int i;

    for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        if (!sm->lea_device[i].sink_device)
        {
            continue;
        }

        if (!sinkServiceLeSm_DeviceIsPaired(sm->lea_device[i].sink_device))
        {
            appDeviceDeleteWithTpAddr(&sm->lea_device[i].tp_acl_hold_addr);

            sm->lea_device[i].sink_device = NULL;
        }
    }
}

sink_service_state_machine_t *SinkServiceLeSm_CreateSm(device_t device)
{
    sink_service_state_machine_t *new_sm = NULL;
    UNUSED(device);

    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state == SINK_SERVICE_STATE_DISCONNECTED)
        {
            new_sm = sm;
            SinkServiceLeSm_ClearInstance(new_sm);
            break;
        }
    }

    return new_sm;
}

sink_service_state_machine_t* SinkServiceLeSm_GetSmForDevice(device_t device)
{
    int i;

    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state > SINK_SERVICE_STATE_DISCONNECTED)
        {
            for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
            {
                if (sm->lea_device[i].sink_device == device)
                {
                    return sm;
                }
            }
        }
    }

    return NULL;
}

/* Get the sink service state machine from TP Address */
sink_service_state_machine_t* SinkServiceLeSm_GetSmFromTpaddr(tp_bdaddr *tpaddr)
{
    sink_service_state_machine_t *sm_match = NULL;

    DEBUG_LOG_VERBOSE("sinkServiceLeSm_GetSmFromTpaddr Searching for addr [%04x,%02x,%06lx]",
                       tpaddr->taddr.addr.nap,
                       tpaddr->taddr.addr.uap,
                       tpaddr->taddr.addr.lap);

    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state > SINK_SERVICE_STATE_DISCONNECTED &&
            sinkServiceLeSm_IsLeDevicePresent(sm, tpaddr))
        {
            sm_match = sm;
            break;
        }
    }

    return sm_match;
}

void SinkServiceLeSm_ClearInstance(sink_service_state_machine_t *sm)
{
    int i;
    PanicNull(sm);

    memset(sm, 0, sizeof(*sm));
    sm->state = SINK_SERVICE_STATE_DISCONNECTED;
    sm->task_data.handler = sinkService_MainMessageHandler;

    for (i=0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
    {
        sm->lea_device[i].gatt_cid = INVALID_CID;
    }
}

bool SinkServiceLeSm_ConnectRequest(sink_service_state_machine_t *sm)
{
    DEBUG_LOG_INFO("SinkServiceLeSm_ConnectRequest Connecting to existing device...");

    sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_CONNECTING_LE_ACL);

    return TRUE;
}

bool SinkServiceLeSm_DisconnectRequest(sink_service_state_machine_t *sm)
{
    bool status = FALSE;

    DEBUG_LOG_INFO("SinkServiceLeSm_DisconnectRequest");

    if (sinkServiceLeSm_GetStateForSm(sm) > SINK_SERVICE_STATE_DISCONNECTED)
    {
        /* cid is given as zero to disconnect all the devices in the group */
        if (!LeAudioClient_Disconnect(0, TRUE))
        {
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
        }
    }

    return status;
}

/*! \brief Count the number of active LE sink state machines */
unsigned SinkServiceLeSm_GetLeAclConnectionCount(void)
{
    unsigned active_sm_count = 0;
    bdaddr device_address;
    int i;

    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state > SINK_SERVICE_STATE_DISCONNECTED)
        {
            for (i = 0; i < SINK_SERVICE_MAX_SUPPORTED_LEA_DEVICES_IN_SET; i++)
            {
                if (sm->lea_device[i].sink_device != NULL)
                {
                    device_address = DeviceProperties_GetBdAddr(sm->lea_device[i].sink_device);
                }

                if (ConManagerIsConnected(&device_address))
                {
                    active_sm_count++;
                }
            }
        }
    }

    DEBUG_LOG_VERBOSE("SinkServiceLeSm_GetLeAclConnectionCount %u", active_sm_count);

    return active_sm_count;
}

bool SinkServiceLeSm_MaxLeAclConnectionsReached(void)
{
    unsigned num_le_connections = SinkServiceLeSm_GetLeAclConnectionCount();
    unsigned max_le_connections = sinkService_LeAclMaxConnections();

    DEBUG_LOG_DEBUG("SinkServiceLeSm_MaxLeAclConnectionsReached  %u of %u LE connections", num_le_connections, max_le_connections);

    return num_le_connections >= max_le_connections;
}

void SinkServiceLeSm_DisableAll(void)
{
    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state == SINK_SERVICE_STATE_DISCONNECTED)
        {
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISABLED);
        }
    }
}

void SinkServiceLeSm_EnableAll(void)
{
    FOR_EACH_SINK_SM(sm)
    {
        if (sm->state == SINK_SERVICE_STATE_DISABLED)
        {
            sinkServiceLeSm_SetState(sm, SINK_SERVICE_STATE_DISCONNECTED);
        }
    }
}

void SinkServiceLeSm_RegisterForGattNotifications(void)
{
    GattConnect_RegisterObserver(&sink_service_le_sm_gatt_callback);
}

#endif /* ENABLE_LE_SINK_SERVICE */

